package cn.bqjr.eily.core.log;

import cn.bqjr.eily.core.annotation.AutoLogable;
import cn.bqjr.eily.core.constants.Constants;
import cn.bqjr.eily.utils.IdWorker.IdWorker;
import cn.bqjr.eily.utils.StringUtils;
import javassist.*;
import javassist.bytecode.LocalVariableAttribute;
import org.apache.commons.lang3.ArrayUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.util.CollectionUtils;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

/**
 * 日志AOP工具类
 */
public class LogAopUtils {

    public static final String requestId = "requestId";
    public static final String args = "args";
    public static final String resp = "resp";
    public static final String createdDate = "createdDate";
    public static final String descp = "descp";
    public static final String methodName = "methodName";

    /**
     * 初始化AOP日志实体信息
     *
     * @param point
     * @param request
     * @return
     */
    public static LogAopEntity genarate(JoinPoint point, HttpServletRequest request) {
        LogAopEntity entity = new LogAopEntity().setPoint(point)
                                                .setMethodName(point.getSignature().getName());
        Method method = ((MethodSignature) point.getSignature()).getMethod();
        if (method.isAnnotationPresent(AutoLogable.class)) {
            AutoLogable anno = method.getAnnotation(AutoLogable.class);
            entity.setLogAnno(anno);
        }

        boolean isHttpRequest = (null != request);
        entity.setHttpRequest(isHttpRequest);
        entity.setRequest(request);
        if (isHttpRequest) {
            Object reqIdObj = request.getAttribute(Constants.REQ_ID);
            if (null != reqIdObj) {
                entity.setRequestId(String.valueOf(reqIdObj));
            } else {
                entity.setRequestId(IdWorker.get32UUID());
                request.setAttribute(Constants.REQ_ID, entity.getRequestId()); // requestId
            }
        }
        return entity;
    }

    /**
     * 获取请求信息
     *
     * @param entity
     * @return
     * @throws NotFoundException
     */
    public static Map<String, Object> getRequestInfo(LogAopEntity entity) throws NotFoundException {
        Map<String, Object> reqMap = new HashMap<>();
        reqMap.put(requestId, entity.getRequestId());
        reqMap.put(methodName, entity.getMethodName());
        reqMap.put(createdDate, new Date());
        reqMap.put(args, getMethodArgsKv(entity));
        if (entity.getLogAnno() != null) {
            reqMap.put(descp, entity.getLogAnno().descp());
        }
        return reqMap;
    }

    /**
     * 获取方法参数信息
     *
     * @param entity
     * @return
     * @throws NotFoundException
     */
    public static Map<String, Object> getMethodArgsKv(LogAopEntity entity) throws NotFoundException {
        Map<String, Object> paramNames = getMethodArgsName(entity);
        Object[] args = entity.getPoint().getArgs();
        if (CollectionUtils.isEmpty(paramNames)) {
            return Collections.EMPTY_MAP;
        }

        Map<String, Object> paramsKvMap = new HashMap<>();
        paramNames.forEach((k, v) -> {
            int index = Integer.valueOf(String.valueOf(v));
            Object arg = (null == args[index] ? "" : args[index]);
            paramsKvMap.put(k, arg);
        });
        return paramsKvMap;
    }

    /**
     * 获取方法参数名列表
     *
     * @param entity
     * @return
     * @throws NotFoundException
     */
    private static Map<String, Object> getMethodArgsName(LogAopEntity entity) throws NotFoundException {
        if (null == entity) {
            return Collections.EMPTY_MAP;
        }

        String[] emptyArr = new String[0];
        String[] includeArr = emptyArr;
        if (null != entity.getLogAnno()) {
            includeArr = entity.getLogAnno().include();
            if (includeArr.length == 1 && StringUtils.isEmpty(includeArr[0])) {
                includeArr = emptyArr;
            }
        }
        Class evtClass = entity.getPoint().getTarget().getClass();
        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(evtClass));
        CtMethod cm = pool.get(evtClass.getName()).getDeclaredMethod(entity.getMethodName());
        LocalVariableAttribute attr =
                (LocalVariableAttribute) cm.getMethodInfo()
                                           .getCodeAttribute()
                                           .getAttribute(LocalVariableAttribute.tag);
        if (null != attr) {
            Map<String, Object> paramNames = new HashMap<>();
            int paramNamesLen = cm.getParameterTypes().length;
            int pos = Modifier.isStatic(cm.getModifiers()) ? 0 : 1;

            // 返回全部字段
            if (ArrayUtils.isEmpty(includeArr)) {
                for (int i = 0; i < paramNamesLen; i++) {
                    paramNames.put(attr.variableName(i + pos), i);
                }
            } else { //仅仅返回includeArr中的字段
                for (int i = 0; i < paramNamesLen; i++) {
                    String paramName = attr.variableName(i + pos);
                    if (ArrayUtils.contains(includeArr, paramName)) {
                        paramNames.put(paramName, i);
                    }
                }
            }
            return paramNames;
        }

        return Collections.EMPTY_MAP;
    }

    /**
     * 获取返回信息
     *
     * @param entity
     * @param obj
     * @return
     */
    public static Map<String, Object> getResponseInfo(LogAopEntity entity, Object obj) {
        Map<String, Object> respMap = new HashMap<>(3);
        respMap.put(requestId, entity.getRequestId());
        respMap.put(createdDate, new Date());
        if (null != obj) {
            respMap.put(resp, obj);
        }
        return respMap;
    }
}
